using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SystemUI;

namespace BesAsm.Framework.ArcGisUtilities
{
  /// <summary>
  /// A class for performing GIS operations to faciliate tracing.
  /// </summary>
  [Guid("d36d15f5-5ccc-4d12-a9d3-4cbdf185c233")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("BesAsm.Framework.Utils.ArcGisUtils")]
  public class ArcGisUtils
  {
    private IApplication _arcApp;

    public ArcGisUtils(IApplication arcApp)
    {
      this._arcApp = arcApp;
    }

    /// <summary>
    /// Visual indicator for flagging geometry of interest to the user
    /// </summary>
    /// <param name="geom">Geometry of interest</param>
    /// <param name="map">Map on which to perform visual indicator</param>
    public void FlashGeometry(IGeometry geom, IMap map)
    {
      ISymbol symbol;
      ILineSymbol lineSymbol;
      ISimpleFillSymbol fillSymbol;
      IMarkerSymbol markerSymbol;
      IScreenDisplay display;
      IRgbColor color;
      IActiveView activeView;

      color = new RgbColor();
      color.Red = 50;
      color.Blue = 175;
      color.Green = 50;

      activeView = (IActiveView)map;
      display = activeView.ScreenDisplay;

      display.StartDrawing(0, -1);

      int sleepTime = 500;

      switch (geom.GeometryType)
      {
        case esriGeometryType.esriGeometryEnvelope:
          object missing = Type.Missing;
          Polygon poly;
          poly = new PolygonClass();
          poly.AddPoint(geom.Envelope.UpperLeft, ref missing, ref missing);
          poly.AddPoint(geom.Envelope.UpperRight, ref missing, ref missing);
          poly.AddPoint(geom.Envelope.LowerRight, ref missing, ref missing);
          poly.AddPoint(geom.Envelope.LowerLeft, ref missing, ref missing);
          poly.AddPoint(geom.Envelope.UpperLeft, ref missing, ref missing);
          fillSymbol = new SimpleFillSymbolClass();
          symbol = (ISymbol)fillSymbol;
          symbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen;
          fillSymbol.Color = color;
          display.SetSymbol(symbol);
          display.DrawPolygon((IGeometry)poly);
          System.Threading.Thread.Sleep(sleepTime);
          display.DrawPolygon((IGeometry)poly);
          break;
        case esriGeometryType.esriGeometryPolyline:
          lineSymbol = new SimpleLineSymbolClass();
          symbol = (ISymbol)lineSymbol;
          symbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen;
          lineSymbol.Width = 4;
          lineSymbol.Color = color;
          display.SetSymbol(symbol);
          display.DrawPolyline(geom);
          System.Threading.Thread.Sleep(sleepTime);
          display.DrawPolyline(geom);
          break;
        case esriGeometryType.esriGeometryPolygon:
          fillSymbol = new SimpleFillSymbolClass();
          symbol = (ISymbol)fillSymbol;
          symbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen;
          fillSymbol.Color = color;
          ILineSymbol outlineSymbol;
          outlineSymbol = new SimpleLineSymbolClass();
          outlineSymbol.Color = color;
          outlineSymbol.Width = 0;
          fillSymbol.Outline = (ILineSymbol)outlineSymbol;
          display.SetSymbol(symbol);
          display.DrawPolygon(geom);
          System.Threading.Thread.Sleep(sleepTime);
          display.DrawPolygon(geom);
          break;
        case esriGeometryType.esriGeometryPoint:
          markerSymbol = new SimpleMarkerSymbolClass();
          symbol = (ISymbol)markerSymbol;
          symbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen;
          markerSymbol.Color = color;
          display.SetSymbol(symbol);
          display.DrawPolygon(geom);
          System.Threading.Thread.Sleep(sleepTime);
          display.DrawPolygon(geom);
          break;
        case esriGeometryType.esriGeometryMultipoint:
          markerSymbol = new SimpleMarkerSymbolClass();
          symbol = (ISymbol)markerSymbol;
          symbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen;
          markerSymbol.Color = color;
          display.SetSymbol(symbol);
          display.DrawMultipoint(geom);
          System.Threading.Thread.Sleep(sleepTime);
          display.DrawMultipoint(geom);
          break;
        default:
          break;
      }
      activeView.PartialRefresh(ESRI.ArcGIS.Carto.esriViewDrawPhase.esriViewGraphics, null, null);
      display.FinishDrawing();

      return;
    }

    /// <summary>
    /// Returns a circle centered around a given point and radius
    /// </summary>
    /// <param name="point">The circle's center coordinates</param>
    /// <param name="radius">The radius of the circle</param>
    /// <returns>The circle</returns>
    public IGeometry GetCircularGeometry(IPoint point, double radius)
    {
      IConstructCircularArc circularArc;
      circularArc = new CircularArcClass();
      circularArc.ConstructCircle(point, radius, true);

      IPolygon polygon;
      polygon = new PolygonClass();
      polygon.SpatialReference = point.SpatialReference;

      ISegmentCollection segmentCollection;
      segmentCollection = (ISegmentCollection)polygon;
      object missing = Type.Missing;
      segmentCollection.AddSegment((ISegment)circularArc, ref missing, ref missing);

      return (IGeometry)polygon;
    }

    public void ChangeSymbol(IFeatureLayer layer, ISymbol symbol)
    {
      IGeoFeatureLayer gfl = (IGeoFeatureLayer)layer;

      ISimpleRenderer renderer = new SimpleRendererClass();
      renderer.Symbol = symbol;

      gfl.Renderer = renderer as IFeatureRenderer;
    }

    /// <summary>
    /// Returns a layer from the current document by name
    /// </summary>
    /// <param name="layerName">The name of the layer to retrieve</param>
    /// <returns>A layer in the current map named layerName</returns>
    public IFeatureLayer GetFeatureLayer(string layerName)
    {
      List<ILayer> layers = GetAllLayersInMap();

      foreach (ILayer layer in layers)
      {
        if (layer.Name == layerName && layer is IFeatureLayer)
          return layer as IFeatureLayer;
      }
      return null;
    }

    /// <summary>
    /// Returns a layer from the current document by name
    /// </summary>
    /// <param name="layerName">The name of the layer to retrieve</param>
    /// <returns>A layer in the current map named layerName</returns>
    public IGroupLayer GetGroupLayer(string layerName)
    {
      List<ILayer> layers = GetAllLayersInMap();

      foreach (ILayer layer in layers)
      {
        if (layer.Name == layerName && layer is IGroupLayer)
          return layer as IGroupLayer;
      }
      return null;
    }

    /// <summary>
    /// Creates a temporary layer from the selected features in an existing layer
    /// </summary>
    /// <param name="selectedLayerName">The name of a layer used to create the new layer</param>
    /// <param name="newLayerName">The name of the new layer to be created. Any existing layers with the same
    /// name will be removed from the map</param>
    /// <param name="groupName">The name of the group layer to put the new layer in. Will be created if it does not 
    /// already exist.</param>
    /// <returns>A reference to the newly created feature layer</returns>
    public IFeatureLayer CreateLayerFromSelection(string selectedLayerName, string newLayerName, string groupName)
    {
      IMxDocument doc = (IMxDocument)_arcApp.Document;
      List<ILayer> layers = GetAllLayersInMap();

      IFeatureLayer newLayer = null;
      IFeatureLayer selectedLayer = null;

      foreach (ILayer layer in layers)
      {
        if (layer.Name == selectedLayerName)
          selectedLayer = (IFeatureLayer)layer;
        if (layer.Name == newLayerName)
          newLayer = (IFeatureLayer)layer;
      }

      RemoveLayers(newLayerName);

      IFeatureLayerDefinition fld = (IFeatureLayerDefinition)selectedLayer;
      IFeatureLayer fl = fld.CreateSelectionLayer(newLayerName, true, null, null);

      MoveLayerToGroup(fl, groupName);

      doc.ActiveView.Refresh();
      doc.UpdateContents();

      return fl;
    }

    /// <summary>
    /// Removes all layers named layerName from the current document
    /// </summary>
    /// <param name="layerName">The name of the layer file(s) to be removed</param>
    /// <returns>Returns true if layerName was found and removed, otherwise false</returns>
    public bool RemoveLayers(string layerName)
    {
      List<ILayer> layers = GetAllLayersInMap();

      foreach (ILayer l in layers)
      {
        if (l.Name == layerName)
        {
          GetFocusMap().DeleteLayer(l);
          RemoveLayers(layerName);
          return true;
        }
      }
      return false;
    }

    /// <summary>
    /// Moves a layer in the current document to a group layer. If the group layer does not exist
    /// it will be created.
    /// </summary>
    /// <param name="layerName">The name of the layer to add to a group layer</param>
    /// <param name="groupName">The name of the group layer</param>
    public void MoveLayerToGroup(IFeatureLayer layer, string groupName)
    {
      IMxDocument doc = (IMxDocument)_arcApp.Document;

      IGroupLayer groupLayer = GetGroupLayer(groupName);
      if (groupLayer == null)
      {
        groupLayer = new GroupLayerClass();
        groupLayer.Name = groupName;
        doc.AddLayer(groupLayer as ILayer);
      }

      groupLayer.Add(layer);

      //HACK: When adding a layer to a group, we must manually call the LayerAdded event.
      //http://forums.arcgis.com/threads/8209-IActiveViewEvents-ItemAdded-and-Group-Layers
      IMapAdmin3 mapAdmin = (IMapAdmin3)doc.FocusMap;
      mapAdmin.FireLayerAdded(layer);

      doc.UpdateContents();
      doc.ActiveView.Refresh();
      
    }

    /// <summary>
    /// Gets the active map
    /// </summary>
    /// <returns>The active map in the current Arc session</returns>
    public IMap GetFocusMap()
    {
      IMxDocument doc = _arcApp.Document as IMxDocument;
      return doc.FocusMap;
    }

    /// <summary>
    /// Returns a list of all layers in the current map, including nested layers and group layers
    /// </summary>    
    /// <returns>A List of all layers in the active map</returns>
    public List<ILayer> GetAllLayersInMap()
    {
      IMap map = GetFocusMap();
      return GetAllLayersInMap(map);
    }

    /// <summary>
    /// Returns a list of all layers in the provided map, including nested layers and group layers
    /// </summary>    
    /// <param name="map">The IMap to retrieve layers from</param>
    /// <returns>A List of all layers in the provided map</returns>
    public List<ILayer> GetAllLayersInMap(IMap map)
    {
      List<ILayer> oLayerList = new List<ILayer>();
      try
      {
        for (int i = 0; i < map.LayerCount; i++)
        {
          ILayer ipLayer = map.get_Layer(i);
          if (ipLayer is IGroupLayer)
          {
            GetAllLayersInGroupLayer(ipLayer as IGroupLayer, ref oLayerList);
          }
          if (ipLayer.Valid)
          {
            oLayerList.Add(ipLayer);
          }
        }

        if (oLayerList.Count == 0)
        {
          return oLayerList;
        }
      }
      catch (Exception ex)
      {
        throw (ex);
      }
      return oLayerList;
    }

    /// <summary>
    /// Gets all the layers inside a group layer
    /// </summary>
    /// <param name="ipGroupLayer">The group layer of interest</param>
    /// <param name="oLayerList">The list that will contain the retrieved layers</param>
    private void GetAllLayersInGroupLayer(IGroupLayer ipGroupLayer, ref List<ILayer> oLayerList)
    {
      try
      {
        ICompositeLayer ipCompositeLayer = ipGroupLayer as ICompositeLayer;
        for (int i = 0; i < ipCompositeLayer.Count; i++)
        {
          ILayer ipLayer = ipCompositeLayer.get_Layer(i);
          if (ipLayer is IGroupLayer)
          {
            oLayerList.Add(ipLayer);
            GetAllLayersInGroupLayer(ipLayer as IGroupLayer, ref oLayerList);
          }
          else if (ipLayer.Valid)
          {
            oLayerList.Add(ipLayer);

          }
        }
      }
      catch (Exception ex)
      {
        throw (ex);
      }
    }

    /// <summary>
    /// Adds a layer to the current focused map
    /// </summary>
    /// <param name="layer">The layer of interest</param>
    public void AddLayerToMap(ILayer layer)
    {
      IMap map = GetFocusMap();
      map.AddLayer(layer);
    }

    /// <summary>
    /// Zooms to the current selection
    /// </summary>
    public void ZoomToSelected()
    {
      IMap map = GetFocusMap();
      if (map.SelectionCount > 0)
      {
        IEnvelope selectedEnvelope = new EnvelopeClass();
        IEnumFeature enumFeature = (IEnumFeature)map.FeatureSelection;
        IFeature feature = enumFeature.Next();
        while (feature != null)
        {
          selectedEnvelope.Union(feature.Shape.Envelope);
          feature = enumFeature.Next();
        }

        selectedEnvelope.Expand(1.2, 1.2, true);
        IMxDocument document = _arcApp.Document as IMxDocument;
        IActiveView activeView = document.ActiveView;
        activeView.Extent = selectedEnvelope;
        activeView.Refresh();
      }
    }

    /// <summary>
    /// Zooms to the specified layer
    /// </summary>
    /// <param name="layer">Layer of interest</param>
    public void ZoomToLayer(IFeatureLayer layer)
    {
      IMxDocument document = _arcApp.Document as IMxDocument;
      IActiveView activeView = document.ActiveView;
      ISpatialReference activeViewSpatialReference = activeView.FocusMap.SpatialReference;

      ISelectionSet layerSelection = (layer as IFeatureLayerDefinition).DefinitionSelectionSet;
      IEnvelope layerEnvelope = new EnvelopeClass();
      if (layerSelection.Count > 0)
      {
        ICursor cursor;
        layerSelection.Search(null, true, out cursor);
        IFeatureCursor featureCursor = cursor as IFeatureCursor;
        IFeature feature = featureCursor.NextFeature();
        while (feature != null)
        {
          IGeometry featureShape = feature.ShapeCopy;
          featureShape.Project(activeViewSpatialReference);
          IEnvelope featureEnvelope = new EnvelopeClass();
          featureShape.Envelope.QueryEnvelope(featureEnvelope);

          int linkNameFieldIndex = -1;
          for (int i = 0; i < feature.Fields.FieldCount; i++)
          {
            if (feature.Fields.get_Field(i).Name == "link_name")
            {
              linkNameFieldIndex = i;
              break;
            }
          }

          string linkName = (linkNameFieldIndex != 0) ? feature.get_Value(linkNameFieldIndex).ToString()
            : "NoLink";

          layerEnvelope.Union(featureEnvelope);
          feature = featureCursor.NextFeature();
        }
      }
      else
      {
        layerEnvelope = layer.AreaOfInterest;
      }

      layerEnvelope.Expand(1.1, 1.1, true);

      activeView.Extent = layerEnvelope;
      activeView.Refresh();
    }

    public string GetCatalogUserDataPath()
    {
      string applicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
      string arcCatalogPath = System.IO.Path.Combine(applicationDataPath, @"ESRI\Desktop10.1\ArcCatalog");

      return System.IO.Path.Combine(applicationDataPath, arcCatalogPath);

      
    }
  }
}
